// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: xguo, daniel

#pragma once

#include <memory>
#include <string>

#include <fmt/format.h>
#include <google/protobuf/util/json_util.h>
#include <pybind11/functional.h>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include "coin2/exchange/base/order/order.h"

namespace py = pybind11;

namespace coin2::exchange::driver {
namespace impl {

using coin::proto::OrderGatewayLog;
using coin::proto::RiskEvent;
using coin2::exchange::base::order::OrderContext;
using coin2::exchange::base::order::OrderSpec;
using coin2::exchange::base::order::OrderUpdate;

template <class T>
auto ProtoToDict(T&& proto) -> py::dict {
  auto s = ProtoToJson(proto);
  return py::module_::import("json").attr("loads")(s);
}

auto GenOrderSpecFromDict(
    coin2::exchange::base::order::BaseOrderSubsystem* os,
    py::dict order_spec_dict) -> std::optional<OrderSpec> {
  using namespace coin2::exchange::base::order;
  using namespace coin::proto;
  try {
    coin::proto::ExchangeType exchange_type;
    auto exchange_type_str = order_spec_dict["exchange_type"].cast<std::string>();
    coin::proto::ExchangeType_Parse(exchange_type_str, &exchange_type);

    auto market_type_str = order_spec_dict["market_type"].cast<std::string>();
    coin::proto::MarketType market_type;
    coin::proto::MarketType_Parse(market_type_str, &market_type);

    auto order_type_str = order_spec_dict["order_type"].cast<std::string>();
    coin::proto::OrderType order_type;
    coin::proto::OrderType_Parse(order_type_str, &order_type);

    auto order_side_str = order_spec_dict["order_side"].cast<std::string>();
    coin::proto::OrderSide order_side;
    coin::proto::OrderSide_Parse(order_side_str, &order_side);

    auto order_duration_str = order_spec_dict["order_duration"].cast<std::string>();
    coin::proto::OrderDuration order_duration;
    coin::proto::OrderDuration_Parse(order_duration_str, &order_duration);

    auto norm_product = order_spec_dict["norm_product"].cast<std::string>();
    auto timestamp = order_spec_dict["timestamp"].cast<int64_t>();

    auto product =
        CreateProductFromNormStringNoThrow(market_type, exchange_type, norm_product, timestamp);
    if (!product) {
      return {};
    }
    auto* product_holder = os->GetProductHolder(*product, timestamp);

    auto order_price = order_spec_dict["order_price"].cast<double>();
    auto* tick_qty = &product_holder->product_info().tick_qty();
    auto order_qty = tick_qty->Floor(order_spec_dict["order_qty"].cast<double>());

    OrderSpec order_spec(
        order_spec_dict["product"].cast<std::string>(),
        norm_product,
        exchange_type,
        market_type,
        order_type,
        order_side,
        order_duration,
        order_price,
        order_qty,
        timestamp,
        order_spec_dict["post_only"].cast<bool>(),
        order_spec_dict["reduce_only"].cast<bool>(),
        order_spec_dict["leverage_rate"].cast<double>(),
        order_spec_dict["tag"].cast<std::string>());
    return order_spec;
  } catch (...) {
    return {};
  }
}

auto OrderContextToDict(const OrderContext& order_context) -> py::dict {
  using namespace pybind11::literals;
  return py::dict(
      "market"_a = coin::proto::MarketType_Name(order_context.order_spec().market_type),
      "exchange"_a = coin::proto::ExchangeType_Name(order_context.order_spec().exchange),
      // "relative_norm"_a = order_context.order_spec().norm_product,
      "absolute_norm"_a = order_context.order_spec().norm_product,
      "native_symbol"_a = order_context.order_spec().product,
      "proc_order_id"_a = order_context.proc_order_id(),
      "external_order_id"_a = order_context.exchange_order_id(),
      "order_side"_a = coin::proto::OrderSide_Name(order_context.order_side()),
      "order_price"_a = order_context.order_price(),
      "order_qty"_a = order_context.order_qty(),
      "filled_qty"_a = order_context.filled_qty(),
      "tag"_a = order_context.tag(),
      "order_created_time"_a = order_context.order_created_time());
}

class PyOrderUpdate {
 public:
  explicit PyOrderUpdate(const OrderUpdate* upd) : upd_(upd) {}
  ~PyOrderUpdate() = default;

  auto type_str() const -> std::string { return OrderUpdateType_Name(upd_->update_type()); }
  auto mea_str() const -> std::string { return upd_->mea().String(); }
  auto market_str() const -> std::string {
    return coin::proto::MarketType_Name(upd_->mea().market);
  }
  auto exchange_str() const -> std::string {
    return coin::proto::ExchangeType_Name(upd_->mea().exchange);
  }

  auto relative_norm() const -> const std::string& { return upd_->product().relative_norm(); }
  auto absolute_norm() const -> const std::string& { return upd_->product().absolute_norm(); }
  auto native_symbol() const -> const std::string& { return upd_->product().order_native_symbol(); }
  auto timestamp() const -> int64_t { return upd_->timestamp(); }

  auto event() const -> const coin::proto::OrderEvent& {
    return upd_->product_order_info().event();
  }
  auto event_as_dict() const -> py::dict { return ProtoToDict(upd_->product_order_info().event()); }
  auto order_price() const -> double { return upd_->product_order_info().event().order_price(); }
  auto order_qty() const -> double { return upd_->product_order_info().event().order_qty(); }
  auto fill_price() const -> double { return upd_->product_order_info().event().fill_price(); }
  auto fill_qty() const -> double { return upd_->product_order_info().event().fill_qty(); }
  auto order_side() const -> std::string {
    return coin::proto::OrderSide_Name(upd_->product_order_info().event().order_side());
  }
  auto order_type() const -> std::string {
    return coin::proto::OrderType_Name(upd_->product_order_info().event().order_type());
  }
  auto order_duration() const -> std::string {
    return coin::proto::OrderDuration_Name(upd_->product_order_info().event().duration());
  }
  auto tag() const -> std::string { return upd_->product_order_info().event().tag(); }

  auto as_dict() const -> py::dict {
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = type_str(),
        "mea"_a = mea_str(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "native_symbol"_a = native_symbol(),
        "timestamp"_a = timestamp(),
        "event"_a = event_as_dict());
  }

 private:
  const OrderUpdate* upd_;
};

class PyOrderUpdatePickleable {
 public:
  explicit PyOrderUpdatePickleable(
      const std::string& type_str,
      const std::string& mea_str,
      const std::string& market_str,
      const std::string& exchange_str,
      const std::string& relative_norm,
      const std::string& absolute_norm,
      const std::string& native_symbol,
      int64_t timestamp,
      py::dict event)
      : type_str_(type_str),
        mea_str_(mea_str),
        market_str_(market_str),
        exchange_str_(exchange_str),
        relative_norm_(relative_norm),
        absolute_norm_(absolute_norm),
        native_symbol_(native_symbol),
        timestamp_(timestamp),
        event_(event) {}
  ~PyOrderUpdatePickleable() = default;

  auto type_str() const -> const std::string& { return type_str_; }
  auto mea_str() const -> const std::string& { return mea_str_; }
  auto market_str() const -> const std::string& { return market_str_; }
  auto exchange_str() const -> const std::string& { return exchange_str_; }

  auto relative_norm() const -> const std::string& { return relative_norm_; }
  auto absolute_norm() const -> const std::string& { return absolute_norm_; }
  auto native_symbol() const -> const std::string& { return native_symbol_; }
  auto timestamp() const -> int64_t { return timestamp_; }

  auto event_as_dict() const -> py::dict { return event_; }
  auto event_type() const -> std::string { return event_["type"].cast<std::string>(); }
  auto proc_order_id() const -> int64_t {
    return stoll(event_["proc_order_id"].cast<std::string>());
  }
  auto order_id() const -> std::string {
    try {
      return event_["external_order_id"].cast<std::string>();
    } catch (...) {
      return "";
    }
  }
  auto order_price() const -> double { return event_["order_price"].cast<double>(); }
  auto order_qty() const -> double { return event_["order_qty"].cast<double>(); }
  auto fill_price() const -> double {
    try {
      return event_["fill_price"].cast<double>();
    } catch (...) {
      return 0.;
    }
  }
  auto fill_qty() const -> double {
    try {
      return event_["fill_qty"].cast<double>();
    } catch (...) {
      return 0.;
    }
  }
  auto order_side() const -> std::string { return event_["order_side"].cast<std::string>(); }
  auto order_type() const -> std::string { return event_["order_type"].cast<std::string>(); }
  auto order_duration() const -> std::string { return event_["duration"].cast<std::string>(); }
  auto tag() const -> std::string { return event_["tag"].cast<std::string>(); }
  auto error_code() const -> int {
    try {
      return stoi(event_["error_code"].cast<std::string>());
    } catch (...) {
      return 0;
    }
  }
  auto exchange_error_code() const -> std::string {
    try {
      return event_["exchange_error_code"].cast<std::string>();
    } catch (...) {
      return "";
    }
  }
  auto exchange_error_msg() const -> std::string {
    try {
      return event_["exchange_error_msg"].cast<std::string>();
    } catch (...) {
      return "";
    }
  }

  auto as_dict() const -> py::dict {
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = type_str(),
        "mea"_a = mea_str(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "relative_norm"_a = relative_norm(),
        "absolute_norm"_a = absolute_norm(),
        "native_symbol"_a = native_symbol(),
        "timestamp"_a = timestamp(),
        "event"_a = event_as_dict());
  }

 private:
  const std::string type_str_;
  const std::string mea_str_;
  const std::string market_str_;
  const std::string exchange_str_;
  const std::string relative_norm_;
  const std::string absolute_norm_;
  const std::string native_symbol_;
  int64_t timestamp_;
  py::dict event_;
};

class PyAccountInfo {
 public:
  explicit PyAccountInfo(const OrderUpdate* upd) : upd_(upd) {}
  ~PyAccountInfo() = default;

  auto type_str() const -> std::string {
    return coin::proto::OrderUpdateType_Name(upd_->update_type());
  }
  auto mea_str() const -> std::string { return upd_->mea().String(); }
  auto exchange_str() const -> std::string {
    return coin::proto::ExchangeType_Name(upd_->mea().exchange);
  }
  auto market_str() const -> std::string {
    return coin::proto::MarketType_Name(upd_->mea().market);
  }
  auto owner() const -> std::string { return upd_->account_info().owner(); }
  auto timestamp() const -> int64_t { return upd_->timestamp(); }
  bool is_position_update() const { return upd_->account_info().is_position_update(); }
  auto get_account_balance() const -> py::dict {
    return ProtoToDict(upd_->account_info().GetAccountBalance());
  }
  auto get_account_position() const -> py::dict {
    return ProtoToDict(upd_->account_info().GetAccountPosition());
  }
  auto as_dict() const -> py::dict {
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = type_str(),
        "mea"_a = mea_str(),
        "market"_a = market_str(),
        "exchange"_a = exchange_str(),
        "owner"_a = owner(),
        "timestamp"_a = timestamp(),
        "is_position_update"_a = is_position_update(),
        "account_balance"_a = get_account_balance(),
        "account_position"_a = get_account_position());
  }

 private:
  const OrderUpdate* upd_;
};

class PyAccountInfoPickleable {
 public:
  PyAccountInfoPickleable(
      const std::string& type_str,
      const std::string& mea_str,
      const std::string& market_str,
      const std::string& exchange_str,
      const std::string& owner,
      py::dict account_balance,
      py::dict account_position,
      int64_t timestamp)
      : type_str_(type_str),
        mea_str_(mea_str),
        market_str_(market_str),
        exchange_str_(exchange_str),
        owner_(owner),
        account_balance_(account_balance),
        account_position_(account_position),
        timestamp_(timestamp) {}
  ~PyAccountInfoPickleable() = default;

  auto type_str() const -> std::string { return type_str_; }
  auto mea_str() const -> std::string { return mea_str_; }
  auto market_str() const -> std::string { return market_str_; }
  auto exchange_str() const -> std::string { return exchange_str_; }
  auto owner() const -> std::string { return owner_; }
  auto timestamp() const -> int64_t { return timestamp_; }
  auto get_account_balance() const -> py::dict { return account_balance_; }
  auto get_account_position() const -> py::dict { return account_position_; }
  auto as_dict() const -> py::dict {
    using namespace pybind11::literals;
    return py::dict(
        "type"_a = type_str(),
        "mea"_a = mea_str(),
        "exchange"_a = exchange_str(),
        "market"_a = market_str(),
        "owner"_a = owner(),
        "timestamp"_a = timestamp(),
        "account_balance"_a = get_account_balance(),
        "account_position"_a = get_account_position());
  }

 private:
  const std::string type_str_;
  const std::string mea_str_;
  const std::string market_str_;
  const std::string exchange_str_;
  const std::string owner_;
  py::dict account_balance_;
  py::dict account_position_;
  const int64_t timestamp_;
};

enum ExecutionType : int {
  Unknown = 0,
  Submit = 1,
  Cancel = 2,
  Amend = 3,
};

class PyAccountRisk {
 public:
  explicit PyAccountRisk(const OrderGatewayLog* log) : log_(log) {}
  ~PyAccountRisk() = default;

  auto mea_str() const -> std::string {
    return fmt::format(
        "{}.{}.{}",
        market_str(),
        exchange_str(),
        log_->account_request().api_version());
    ;
  }
  auto market_str() const -> std::string { return log_->account_request().market_type(); }
  auto exchange_str() const -> std::string { return log_->account_request().exchange(); }
  auto owner() const -> std::string { return log_->account_request().owner(); }
  auto is_undetected_position() const -> bool {
    return log_->type() == OrderGatewayLog::RISK_EVENT &&
           log_->risk_event().risk_event_type() == RiskEvent::RISK_UNDETECTED_POSITION;
  }
  auto as_dict() const -> py::dict { return ProtoToDict(log_->risk_event()); }

 private:
  const OrderGatewayLog* log_;
};

class PyExecSpecPickleable {
 public:
  explicit PyExecSpecPickleable(const std::string& os_name) : os_name_(os_name) {}
  ~PyExecSpecPickleable() = default;

  auto os_name() const -> const std::string& { return os_name_; }
  auto exec_type() const -> ExecutionType { return exec_type_; }
  auto order_spec() const -> py::dict { return order_spec_; }
  auto proc_order_id() const -> int64_t { return proc_order_id_; }

  void set_exec_type(ExecutionType exec_type) { exec_type_ = exec_type; }
  void set_order_spec(py::dict order_spec) { order_spec_ = order_spec; }
  void set_proc_order_id(int64_t proc_order_id) { proc_order_id_ = proc_order_id; }
  auto as_dict() const -> py::dict {
    using namespace pybind11::literals;
    return py::dict(
        "os_name"_a = os_name(),
        "exec_type"_a = exec_type(),
        "order_spec"_a = order_spec(),
        "proc_order_id"_a = proc_order_id());
  }

 private:
  const std::string os_name_;
  ExecutionType exec_type_;
  py::dict order_spec_;    // option field, for submit order
  int64_t proc_order_id_;  // option field, for cancel order
};

class PyOrderContext {
 public:
  explicit PyOrderContext(const OrderContext* order_context) : order_context_{order_context} {}

  py::dict as_dict() const { return OrderContextToDict(*order_context_); }

 private:
  const OrderContext* order_context_;
};

class PyOrderSnapshotPickleable {
 public:
  PyOrderSnapshotPickleable(py::list order_snapshot)
      : type_str_("ORDER_SNAPSHOT"), order_snapshot_(order_snapshot) {}
  ~PyOrderSnapshotPickleable() = default;

  auto type_str() const -> const std::string& { return type_str_; }
  auto order_snapshot() const -> py::list { return order_snapshot_; }

 private:
  const std::string type_str_;
  py::list order_snapshot_;
};

inline void AddPyOrderUpdate(py::module* m) {
  py::class_<PyOrderUpdate, std::shared_ptr<PyOrderUpdate>>(*m, "_OrderUpdate")
      .def_property_readonly("type", &PyOrderUpdate::type_str)
      .def_property_readonly("mea", &PyOrderUpdate::mea_str)
      .def_property_readonly("market_str", &PyOrderUpdate::market_str)
      .def_property_readonly("exchange_str", &PyOrderUpdate::exchange_str)
      .def_property_readonly("relative_norm", &PyOrderUpdate::relative_norm)
      .def_property_readonly("absolute_norm", &PyOrderUpdate::absolute_norm)
      .def_property_readonly("native_symbol", &PyOrderUpdate::native_symbol)
      .def_property_readonly("timestamp", &PyOrderUpdate::timestamp)
      .def_property_readonly("order_price", &PyOrderUpdate::order_price)
      .def_property_readonly("order_qty", &PyOrderUpdate::order_qty)
      .def_property_readonly("fill_price", &PyOrderUpdate::fill_price)
      .def_property_readonly("fill_qty", &PyOrderUpdate::fill_qty)
      .def_property_readonly("order_side", &PyOrderUpdate::order_side)
      .def_property_readonly("order_type", &PyOrderUpdate::order_type)
      .def_property_readonly("order_duration", &PyOrderUpdate::order_duration)
      .def_property_readonly("tag", &PyOrderUpdate::tag)
      .def("as_dict", &PyOrderUpdate::as_dict);

  py::class_<PyOrderUpdatePickleable, std::shared_ptr<PyOrderUpdatePickleable>>(
      *m,
      "_OrderUpdatePickleable")
      .def_property_readonly("type", &PyOrderUpdatePickleable::type_str)
      .def_property_readonly("mea", &PyOrderUpdatePickleable::mea_str)
      .def_property_readonly("market_str", &PyOrderUpdatePickleable::market_str)
      .def_property_readonly("exchange_str", &PyOrderUpdatePickleable::exchange_str)
      .def_property_readonly("relative_norm", &PyOrderUpdatePickleable::relative_norm)
      .def_property_readonly("absolute_norm", &PyOrderUpdatePickleable::absolute_norm)
      .def_property_readonly("native_symbol", &PyOrderUpdatePickleable::native_symbol)
      .def_property_readonly("timestamp", &PyOrderUpdatePickleable::timestamp)
      .def_property_readonly("order_price", &PyOrderUpdatePickleable::order_price)
      .def_property_readonly("order_qty", &PyOrderUpdatePickleable::order_qty)
      .def_property_readonly("fill_price", &PyOrderUpdatePickleable::fill_price)
      .def_property_readonly("fill_qty", &PyOrderUpdatePickleable::fill_qty)
      .def_property_readonly("order_side", &PyOrderUpdatePickleable::order_side)
      .def_property_readonly("order_type", &PyOrderUpdatePickleable::order_type)
      .def_property_readonly("order_duration", &PyOrderUpdatePickleable::order_duration)
      .def_property_readonly("tag", &PyOrderUpdatePickleable::tag)
      .def("as_dict", &PyOrderUpdatePickleable::as_dict)
      .def(py::pickle(
          [](const PyOrderUpdatePickleable& p) {  // __getstate__
            /* Return a tuple that fully encodes the state of the object */
            return py::make_tuple(
                p.type_str(),
                p.mea_str(),
                p.market_str(),
                p.exchange_str(),
                p.relative_norm(),
                p.absolute_norm(),
                p.native_symbol(),
                p.timestamp(),
                p.event_as_dict());
          },
          [](py::tuple t) {  // __setstate__
            if (t.size() != 9) {
              CHECK(false) << "invalid parameters!";
            }
            /* Create a new C++ instance */
            PyOrderUpdatePickleable p(
                t[0].cast<std::string>(),
                t[1].cast<std::string>(),
                t[2].cast<std::string>(),
                t[3].cast<std::string>(),
                t[4].cast<std::string>(),
                t[5].cast<std::string>(),
                t[6].cast<std::string>(),
                t[7].cast<int64_t>(),
                t[8].cast<py::dict>());
            return p;
          }));

  py::class_<PyAccountInfo, std::shared_ptr<PyAccountInfo>>(*m, "_AccountInfo")
      .def_property_readonly("type", &PyAccountInfo::type_str)
      .def_property_readonly("mea", &PyAccountInfo::mea_str)
      .def_property_readonly("market", &PyAccountInfo::market_str)
      .def_property_readonly("exchange", &PyAccountInfo::exchange_str)
      .def_property_readonly("owner", &PyAccountInfo::owner)
      .def_property_readonly("is_position_update", &PyAccountInfo::is_position_update)
      .def_property_readonly("account_balance", &PyAccountInfo::get_account_balance)
      .def_property_readonly("account_position", &PyAccountInfo::get_account_position)
      .def_property_readonly("timestamp", &PyAccountInfo::timestamp)
      .def("as_dict", &PyAccountInfo::as_dict);

  py::class_<PyAccountInfoPickleable, std::shared_ptr<PyAccountInfoPickleable>>(
      *m,
      "_AccountInfoPickleable")
      .def_property_readonly("type", &PyAccountInfoPickleable::type_str)
      .def_property_readonly("mea", &PyAccountInfoPickleable::mea_str)
      .def_property_readonly("market", &PyAccountInfoPickleable::market_str)
      .def_property_readonly("exchange", &PyAccountInfoPickleable::exchange_str)
      .def_property_readonly("owner", &PyAccountInfoPickleable::owner)
      .def_property_readonly("account_balance", &PyAccountInfoPickleable::get_account_balance)
      .def_property_readonly("account_position", &PyAccountInfoPickleable::get_account_position)
      .def_property_readonly("timestamp", &PyAccountInfoPickleable::timestamp)
      .def("as_dict", &PyAccountInfoPickleable::as_dict)
      .def(py::pickle(
          [](const PyAccountInfoPickleable& p) {  // __getstate__
            /* Return a tuple that fully encodes the state of the object */
            return py::make_tuple(
                p.type_str(),
                p.mea_str(),
                p.market_str(),
                p.exchange_str(),
                p.owner(),
                p.get_account_balance(),
                p.get_account_position(),
                p.timestamp());
          },
          [](py::tuple t) {  // __setstate__
            if (t.size() != 8) {
              CHECK(false) << "invalid parameters!";
            }
            /* Create a new C++ instance */
            PyAccountInfoPickleable p(
                t[0].cast<std::string>(),
                t[1].cast<std::string>(),
                t[2].cast<std::string>(),
                t[3].cast<std::string>(),
                t[4].cast<std::string>(),
                t[5].cast<py::dict>(),
                t[6].cast<py::dict>(),
                t[7].cast<int64_t>());
            return p;
          }));

  py::class_<PyAccountRisk, std::shared_ptr<PyAccountRisk>>(*m, "_AccountRisk")
      .def_property_readonly("mea", &PyAccountRisk::mea_str)
      .def_property_readonly("market", &PyAccountRisk::market_str)
      .def_property_readonly("exchange", &PyAccountRisk::exchange_str)
      .def_property_readonly("owner", &PyAccountRisk::owner)
      .def_property_readonly("is_undetected_position", &PyAccountRisk::is_undetected_position)
      .def("as_dict", &PyAccountRisk::as_dict);

  py::class_<PyExecSpecPickleable, std::shared_ptr<PyExecSpecPickleable>>(
      *m,
      "PyExecSpecPickleable")
      .def(py::init<const std::string&>())
      .def("os_name", &PyExecSpecPickleable::os_name)
      .def("exec_type", &PyExecSpecPickleable::exec_type)
      .def("order_spec", &PyExecSpecPickleable::order_spec)
      .def("proc_order_id", &PyExecSpecPickleable::proc_order_id)
      .def("set_exec_type", &PyExecSpecPickleable::set_exec_type)
      .def("set_order_spec", &PyExecSpecPickleable::set_order_spec)
      .def("set_proc_order_id", &PyExecSpecPickleable::set_proc_order_id)
      .def("as_dict", &PyExecSpecPickleable::as_dict)
      .def(py::pickle(
          [](const PyExecSpecPickleable& p) {  // __getstate__
            /* Return a tuple that fully encodes the state of the object */
            return py::make_tuple(p.os_name(), p.exec_type(), p.order_spec(), p.proc_order_id());
          },
          [](py::tuple t) {  // __setstate__
            if (t.size() != 4) {
              CHECK(false) << "invalid parameters!";
            }
            /* Create a new C++ instance */
            PyExecSpecPickleable p(t[0].cast<std::string>());
            p.set_exec_type(t[1].cast<ExecutionType>());
            p.set_order_spec(t[2].cast<py::dict>());
            p.set_proc_order_id(t[3].cast<int64_t>());
            return p;
          }));

  py::enum_<ExecutionType>(*m, "ExecutionType")
      .value("Unknown", ExecutionType::Unknown)
      .value("Submit", ExecutionType::Submit)
      .value("Cancel", ExecutionType::Cancel);

  py::class_<PyOrderContext, std::shared_ptr<PyOrderContext>>(*m, "_OrderContext")
      .def("as_dict", &PyOrderContext::as_dict);

  py::class_<PyOrderSnapshotPickleable, std::shared_ptr<PyOrderSnapshotPickleable>>(
      *m,
      "_OrderSnapshotPickleable")
      .def_property_readonly("type", &PyOrderSnapshotPickleable::type_str)
      .def_property_readonly("order_snapshot", &PyOrderSnapshotPickleable::order_snapshot)
      .def(py::pickle(
          [](const PyOrderSnapshotPickleable& p) {  // __getstate__
            /* Return a tuple that fully encodes the state of the object */
            return py::make_tuple(p.order_snapshot());
          },
          [](py::tuple t) {  // __setstate__
            if (t.size() != 1) {
              CHECK(false) << "invalid parameters!";
            }
            /* Create a new C++ instance */
            PyOrderSnapshotPickleable p(t[0].cast<py::list>());
            return p;
          }));
}

}  // namespace impl

}  // namespace coin2::exchange::driver
